/*
 * GamePanel.java
 *
 * Created on September 21, 2008, 5:32 PM
 */
package no.eirikb.bomberman.applet.game;

import no.eirikb.bomberman.applet.*;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import no.eirikb.bomberman.shared.Game;
import no.eirikb.bomberman.shared.Map;
import no.eirikb.bomberman.applet.game.Pos;
import no.eirikb.bomberman.shared.User;
import no.eirikb.bomberman.applet.game.Way;
import no.eirikb.bomberman.shared.clientcommand.ClientEvent;
import no.eirikb.bomberman.shared.img.ImgCharacter;

/**
 *
 * @author  eirikb
 */
public class GamePanel extends javax.swing.JPanel {

    private int actualFPS;
    private Bomberman bomberman;
    private Game game;
    private Graphics og;
    private Image offscreen;
    private Image map;
    private final int FPS = 50;  // not really FPS, ms to sleep...
    private final Color COLOR = Color.YELLOW;
    private final int RESIZE = 200; // Percent resize
    private Hashtable<Integer, Way> wayParse;
    // Dirty hack to prevent keyPressed from spam:
    private long lastTime;
    private int lastCode;

    /** Creates new form GamePanel */
    public GamePanel(Bomberman bomberman) {
        this.bomberman = bomberman;
        wayParse = new Hashtable<Integer, Way>();
        wayParse.put(KeyEvent.VK_LEFT, Way.LEFT);
        wayParse.put(KeyEvent.VK_UP, Way.UP);
        wayParse.put(KeyEvent.VK_RIGHT, Way.RIGHT);
        wayParse.put(KeyEvent.VK_DOWN, Way.DOWN);
        initComponents();
    }

    public void clientEvent(ClientEvent event) {
        System.out.println(event);
        event.execute(game);
    }
    //
    // ********************************** GRAPHICS *********************************
    //
    @Override
    public void paint(Graphics g) {
        g.drawImage(offscreen, 0, 0, this);
    }

    @Override
    public void update(Graphics g) {
        paint(g);
    }

    private BufferedImage drawMap() {
        int w = game.getWidth() * game.getSize();
        int h = game.getHeight() * game.getSize();
        BufferedImage b = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
        Graphics g = b.getGraphics();
        g.setColor(COLOR);
        g.fillRect(0, 0, w, h);
        for (int x = 0; x < game.getWidth(); x++) {
            for (int y = 0; y < game.getHeight(); y++) {
                if (game.getMap(x, y) == Map.BOX) {
                    g.drawImage(bomberman.getIdb().getBox().get("default").getImage(),
                            x * game.getSize(), y * game.getSize(), null);
                }
            }
        }
        return b;
    }

    /**
     * You should not be able to read this! (Too much mess)
     * But the main point is to move the caracter towards where he/she is heading.
     * And he/she it hits a obstacle he/she glides towards the side of it...
     */
    private void moveUsers() {
        for (User u : game.getUsers().values()) {
            double x = u.getPos().getX();
            double y = u.getPos().getY();
            Pos lastPos = u.getLastPos();
            if (lastPos != null &&
                    lastPos.getX() == x && lastPos.getY() == y) {
                u.setLastPos(null);
            }
            lastPos = u.getLastPos();
            if (lastPos != null) {
                Way w = getLastPosWay(u);
                Pos pos = moveObject(u.getPos(), u.getSpeed(), w);
                if (Math.abs(lastPos.getX() - x) <= u.getSpeed() &&
                        Math.abs(lastPos.getY() - y) <= u.getSpeed()) {
                    pos = lastPos;
                    u.setLastPos(null);
                }
                u.setPos(pos);
            }
            if (u.getLastPos() == null && u.isWalking()) {
                switch (u.getWay()) {
                    case LEFT:
                        x -= u.getSpeed();
                        break;
                    case UP:
                        y -= u.getSpeed();
                        break;
                    case RIGHT:
                        x += u.getSpeed();
                        break;
                    case DOWN:
                        y += u.getSpeed();
                        break;
                }
                u.setPos(moveObject(u.getPos(), u.getSpeed(), u.getWay()));
            }
        }
    }

    private Pos moveObject(Pos pos, double speed, Way way) {
        Pos pos2 = moveObjectIgnorant(pos, speed, way);
        double x = pos2.getX();
        double y = pos2.getY();
        if (game.canMove(x, y)) {
            for (int i = (int) speed; i > 0; i--) {
                Pos pos3 = moveObjectIgnorant(pos, i, way);
                if (game.canMoveMapSquare(pos3.getX(), pos3.getY())) {
                    return pos3;
                }
            }
            int x1 = (int) (x / game.getSize());
            int y1 = (int) (y / game.getSize());
            int x2 = (int) ((x - 1) / game.getSize() + 1);
            int y2 = (int) ((y - 1) / game.getSize() + 1);
            switch (way) {
                case LEFT:
                    if (!game.canMoveMap(x1, y1) && !game.canMoveMap(x1, y2)) {
                        return pos;
                    } else if (!game.canMoveMap(x1, y1) && game.canMoveMap(x2, y2) &&
                            game.canMove(x1, y2)) {
                        Pos p = moveObject(pos, speed, Way.DOWN);
                        if ((int) (p.getY() / game.getSize()) != y1) {
                            return new Pos(x, (y1 + 1) * game.getSize());
                        } else {
                            return p;
                        }
                    } else if (!game.canMoveMap(x1, y2) && game.canMoveMap(x1, y1) &&
                            game.canMoveMap(x2, y1)) {
                        Pos p = moveObject(pos, speed, Way.UP);
                        if ((int) (p.getY() / game.getSize()) != y1) {
                            return new Pos(x, y1 * game.getSize());
                        } else {
                            return p;
                        }
                    }
                    break;
                case UP:
                    if (!game.canMoveMap(x1, y1) && !game.canMoveMap(x2, y1)) {
                        return pos;
                    } else if (!game.canMoveMap(x1, y1) && game.canMoveMap(x2, y2) &&
                            game.canMove(x2, y2)) {
                        Pos p = moveObject(pos, speed, Way.RIGHT);
                        if ((int) (p.getX() / game.getSize()) != x1) {
                            return new Pos((x1 + 1) * game.getSize(), y);
                        } else {
                            return p;
                        }
                    } else if (!game.canMoveMap(x2, y1) && game.canMoveMap(x1, y2) &&
                            game.canMoveMap(x1, y1)) {
                        Pos p = moveObject(pos, speed, Way.LEFT);
                        if ((int) (p.getX() / game.getSize()) != x1) {
                            return new Pos(x1 * game.getSize(), y);
                        } else {
                            return p;
                        }
                    }
                    break;
                case RIGHT:
                    if (!game.canMoveMap(x2, y1) && !game.canMoveMap(x2, y2)) {
                        System.out.println("sdf");
                        return pos;
                    } else if (!game.canMoveMap(x2, y1) && game.canMoveMap(x1, y2) &&
                            game.canMove(x2, y2)) {
                        Pos p = moveObject(pos, speed, Way.DOWN);
                        if ((int) (p.getY() / game.getSize()) != y1) {
                            return new Pos(x, (y1 + 1) * game.getSize());
                        } else {
                            return p;
                        }
                    } else if (!game.canMoveMap(x2, y2) && game.canMoveMap(x1, y1) &&
                            game.canMoveMap(x2, y1)) {
                        Pos p = moveObject(pos, speed, Way.UP);
                        if ((int) (p.getY() / game.getSize()) != y1) {
                            return new Pos(x, y1 * game.getSize());
                        } else {
                            return p;
                        }
                    }
                    break;
                case DOWN:
                    if (!game.canMoveMap(x1, y2) && !game.canMoveMap(x2, y2)) {
                        return pos;
                    } else if (!game.canMoveMap(x1, y2) && game.canMoveMap(x2, y1) &&
                            game.canMove(x2, y2)) {
                        Pos p = moveObject(pos, speed, Way.RIGHT);
                        if ((int) (p.getX() / game.getSize()) != x1) {
                            return new Pos((x1 + 1) * game.getSize(), y);
                        } else {
                            return p;
                        }
                    } else if (!game.canMoveMap(x2, y2) && game.canMoveMap(x1, y1) &&
                            game.canMoveMap(x1, y2)) {
                        Pos p = moveObject(pos, speed, Way.LEFT);
                        if ((int) (p.getX() / game.getSize()) != x1) {
                            return new Pos(x1 * game.getSize(), y);
                        } else {
                            return p;
                        }
                    }
                    break;
            }
            return pos2;

        } else {
            // Not insde map!
            if (x < 0) {
                x = 0;
            }
            if (x + game.getSize() > game.getWidth() * game.getSize()) {
                x = game.getWidth() * game.getSize() - game.getSize();
            }
            if (y < 0) {
                y = 0;
            }
            if (y + game.getSize() > game.getHeight() * game.getSize()) {
                y = game.getHeight() * game.getSize() - game.getSize();
            }
        }

        return new Pos(x, y);
    }

    private Pos moveObjectIgnorant(Pos pos, double speed, Way way) {
        switch (way) {
            case LEFT:
                return new Pos(pos.getX() - speed, pos.getY());
            case UP:
                return new Pos(pos.getX(), pos.getY() - speed);
            case RIGHT:
                return new Pos(pos.getX() + speed, pos.getY());
            case DOWN:
                return new Pos(pos.getX(), pos.getY() + speed);
        }
        return null;
    }

    private Way getLastPosWay(User u) {
        double x = u.getPos().getX();
        double y = u.getPos().getY();
        Pos lastPos = u.getLastPos();
        Way w = u.getWay();
        if (Math.abs(lastPos.getX() - x) >= Math.abs(lastPos.getY() - y)) {
            if (lastPos.getX() > x) {
                w = Way.RIGHT;
            } else {
                w = Way.LEFT;
            }
        } else {
            if (lastPos.getY() > y) {
                w = Way.DOWN;
            } else {
                w = Way.UP;
            }
        }
        return w;
    }

    private void drawUsers() {
        for (User u : game.getUsers().values()) {
            Image c = null;
            ImgCharacter imgc = bomberman.getIdb().getCharacter().get(u.getCharacter());
            Way w = u.getWay();
            if (u.getLastPos() != null) {
                w = getLastPosWay(u);
            }
            switch (w) {
                case LEFT:
                    c = imgc.getLeft().get(u.getAnim()).getImage();
                    break;
                case UP:
                    c = imgc.getUp().get(u.getAnim()).getImage();
                    break;
                case RIGHT:
                    c = imgc.getRight().get(u.getAnim()).getImage();
                    break;
                case DOWN:
                    c = imgc.getDown().get(u.getAnim()).getImage();
                    break;
            }
            og.drawImage(c, (int) u.getPos().getX(),
                    (int) u.getPos().getY() - (4 * (game.getSize() / 16)), this);
        }
    }

    private void drawBombs() {
        for (Bomb b : game.getBombs()) {
            Image bomb = bomberman.getIdb().getBomb().get("default").get(b.anim()).getImage();
            og.drawImage(bomb, b.getX() * game.getSize(), b.getY() * game.getSize(), this);
        }
    }

    private void drawFire() {
        Fire[] fires = game.getFires().toArray(new Fire[0]);
        for (Fire f : fires) {
            int anim = f.anim();
            if (anim >= bomberman.getIdb().getFire().get("default").getCore().size()) {
                game.removeFire(f);
            } else {
                if (f.isEnd()) {
                    switch (f.getWay()) {
                        case NOWAI:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getCore().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                        case LEFT:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getLeft().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                        case UP:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getUp().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                        case RIGHT:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getRight().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                        case DOWN:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getDown().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                    }

                } else {
                    switch (f.getWay()) {
                        case LEFT:
                        case RIGHT:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getSide1().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                        case UP:
                        case DOWN:
                            og.drawImage(bomberman.getIdb().getFire().get("default").getSide2().get(anim).getImage(),
                                    f.getX() * game.getSize(), f.getY() * game.getSize(), this);
                            break;
                    }
                }
            }
        }
    }

    private void drawBricks() {
        Brick[] bricks = game.getBricks().toArray(new Brick[0]);
        for (Brick b : bricks) {
            og.drawImage(bomberman.getIdb().getBrick().get("default").get(0).getImage(),
                    b.getX() * game.getSize(), b.getY() * game.getSize(), this);
        }
    }

    private void drawBoomBricks() {
        BoomBrick[] bricks = game.getBoomBricks().toArray(new BoomBrick[0]);
        for (BoomBrick b : bricks) {
            int anim = b.anim();
            if (anim < bomberman.getIdb().getBrick().get("default").size()) {
                og.drawImage(bomberman.getIdb().getBrick().get("default").get(anim).getImage(),
                        b.getX() * game.getSize(), b.getY() * game.getSize(), this);
            } else {
                game.removeBoomBrick(b.getX(), b.getY());
            }
        }
    }

    private void drawPowerUps() {
        PowerUp[] powerUps = game.getPowerUps().toArray(new PowerUp[0]);
        for (PowerUp p : powerUps) {
            if (game.canMoveMap(p.getX(), p.getY())) {
                og.drawImage(bomberman.getIdb().getPowerup().get("default").getImage(),
                        p.getX() * game.getSize(), p.getY() * game.getSize(), this);
            }
        }
    }

    private void checkPowerUp() {
        User me = game.getUsers().get(bomberman.getUser().getNick());
        if (me != null) {
            int x = (int) (me.getPos().getX() / game.getSize());
            int y = (int) (me.getPos().getY() / game.getSize());
            PowerUp p = game.getpowerUp(x, y);
            if (p != null) {
                game.removePowerUp(x, y);
                me.setSpeed(me.getSpeed() + 1);
                try {
                    bomberman.getServer().powerUp(bomberman.getHash(), bomberman.getUser().getNick(), p);
                } catch (RemoteException ex) {
                    Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    public void start(Game game2) {
        this.game = game2;
        bomberman.getIdb().resize(RESIZE);
        int w = game.getWidth() * game.getSize();
        int h = game.getHeight() * game.getSize();
        offscreen = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
        og = offscreen.getGraphics();

        map = drawMap();
        new Thread() {

            public void run() {
                while (getGraphics() == null) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                // HACK FOR TESTING!
                requestFocus();
                // HACK FOR TESTING!
                while (true) {
                    try {
                        long time = System.currentTimeMillis();
                        Thread.sleep(actualFPS);
                        time = System.currentTimeMillis() - time;
                        if (time > FPS) {
                            actualFPS--;
                        } else {
                            actualFPS++;
                        }

                        og.drawImage(map, 0, 0, null);
                        moveUsers();
                        checkPowerUp();
                        drawBombs();
                        drawFire();
                        drawBricks();
                        drawBoomBricks();
                        drawPowerUps();
                        drawUsers();
                        repaint();
                    } catch (InterruptedException ex) {
                        Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }.start();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        setDoubleBuffered(false);
        setRequestFocusEnabled(false);
        setVerifyInputWhenFocusTarget(false);
        addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                formMouseClicked(evt);
            }
        });
        addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                formKeyPressed(evt);
            }
            public void keyReleased(java.awt.event.KeyEvent evt) {
                formKeyReleased(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

private void formKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyReleased
    User me = game.getUsers().get(bomberman.getUser().getNick());
    if (me != null) {
        Way w;
        if ((w = wayParse.get(evt.getKeyCode())) != null) {
            if (w == me.getWay()) {
                me.setWalking(false);
                try {
                    bomberman.getServer().stopWalk(bomberman.getHash(), bomberman.getUser().getNick(), me.getPos());
                } catch (RemoteException ex) {
                    Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                }
                final User meFinal = me;
            /*
            new Thread() {
            
            public void run() {
            try {
            bomberman.getServer().stopWalk(bomberman.getHash(), bomberman.getUser().getNick(), meFinal.getPos());
            } catch (RemoteException ex) {
            Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
            }
            }
            }.start();
             */
            }
        }
    }
}//GEN-LAST:event_formKeyReleased

private void formKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formKeyPressed
    if (evt.getWhen() - lastTime > 90 || evt.getKeyCode() != lastCode) {



        User me = game.getUsers().get(bomberman.getUser().getNick());
        if (me != null) {
            switch (evt.getKeyCode()) {
                case KeyEvent.VK_LEFT:
                case KeyEvent.VK_UP:
                case KeyEvent.VK_RIGHT:
                case KeyEvent.VK_DOWN:
                    Way w;
                    if ((w = wayParse.get(evt.getKeyCode())) != null) {
                        Pos pos = me.getPos();
                        if (!me.isWalking()) {
                            pos = null;
                        }
                        me.setWay(w);
                        me.setWalking(true);
                        final Way wFinal = w;
                        final Pos posFinal = pos;
                        try {
                            bomberman.getServer().startWalk(bomberman.getHash(), bomberman.getUser().getNick(), w, pos);
                        } catch (RemoteException ex) {
                            Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                        }

                    /*
                    new Thread() {
                    
                    public void run() {
                    
                    try {
                    bomberman.getServer().startWalk(bomberman.getHash(), bomberman.getUser().getNick(), wFinal, posFinal);
                    
                    } catch (RemoteException ex) {
                    Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    }
                    }.start();
                     */
                    }
                    break;
                case KeyEvent.VK_SPACE:
                    if (me.getBombs() > 0) {
                        double x = me.getPos().getX();
                        double y = me.getPos().getY();
                        switch (me.getWay()) {
                            case LEFT:
                                x += (game.getSize() / 2);
                                break;
                            case UP:
                                y += (game.getSize() / 2);
                                break;
                            case RIGHT:
                                x -= (game.getSize() / 2);
                                break;
                            case DOWN:
                                y -= (game.getSize() / 2);
                                break;
                        }
                        int x1 = (int) (x / game.getSize());
                        int y1 = (int) (y / game.getSize());
                        if (x1 < 0 || x1 > game.getWidth() ||
                                y1 < 0 || y1 > game.getHeight() ||
                                !game.canMoveMap(x1, y1)) {
                            x1 = (int) (me.getPos().getX() / game.getSize());
                            y1 = (int) (me.getPos().getY() / game.getSize());
                        }
                        Bomb bomb = new Bomb(x1, y1);
                        bomb.setUser(me);
                        bomb.setSize(me.getBombSize());
                        bomb.setTime(me.getBombTime());
                        me.setBombs(me.getBombs() - 1);
                        game.addBomb(bomb);
                        try {

                            bomberman.getServer().setBomb(bomberman.getHash(), bomberman.getUser().getNick(), bomb);
                        } catch (RemoteException ex) {
                            Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        final Bomb bombFinal = bomb;
                        /*
                        new Thread() {
                        
                        public void run() {
                        try {
                        bomberman.getServer().setBomb(bomberman.getHash(),
                        bomberman.getUser().getNick(),
                        bombFinal);
                        } catch (RemoteException ex) {
                        Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        }
                        }.start();
                         */
                        break;
                    }
            }
            lastCode = evt.getKeyCode();
        }
        lastTime = evt.getWhen();
    }
}//GEN-LAST:event_formKeyPressed

private void formMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseClicked
    User me = game.getUsers().get(bomberman.getUser().getNick());
    if (me != null) {
        me.setPos(new Pos(evt.getX(), evt.getY()));
        try {
        bomberman.getServer().stopWalk(bomberman.getHash(), me.getNick(), me.getPos());//GEN-LAST:event_formMouseClicked
            } catch (RemoteException ex) {
                Logger.getLogger(GamePanel.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
}
